package moe.dao;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;

import moe.dao.pool.ConnectionManager;
import moe.util.Paging;

public class BaseDao {
	private final static Logger log = Logger.getLogger(BaseDao.class.getName());

	public static enum ConnType {
		DEFAULT, DUMP, COLLECTION
	}

	private ConnType connType;

	protected BaseDao(ConnType connType) {
		this.connType = connType;
	}

	public ConnType getConnType() {
		return connType;
	}

	private ConnectionManager getConnectionManager() {
		ConnectionManager manager = null;
		switch (connType) {
		case DEFAULT:
			manager = ConnectionManager.getDefaultManager();
			break;
		case COLLECTION:
			manager = ConnectionManager.getCollectionManager();
			break;
		case DUMP:
			manager = ConnectionManager.getDumpManager();
			break;
		default:
			throw new RuntimeException("Unknow connection type:" + connType);
		}
		return manager;
	}

	protected Connection getConnection() throws SQLException {
		return getConnectionManager().getConnection();
	}

	protected Statement createStatement() throws SQLException {
		return getConnection().createStatement();
	}

	protected PreparedStatement prepareStatement(String sql) throws SQLException {
		return getConnection().prepareStatement(sql);
	}

	protected PreparedStatement prepareStatement(String sql, boolean isCache) throws SQLException {
		if (isCache) {
			return prepareStatement(sql);
		} else {
			Connection connection = getConnection();
			return getConnectionManager().getUncachePreparedStatement(sql, connection);
		}
	}

	protected void close(Statement stat) {
		try {
			if (stat != null) {
				Connection conn = stat.getConnection();
				stat.close();
				conn.close();
			}
		} catch (SQLException e) {
			// e.printStackTrace();
			log.throwing(BaseDao.class.getName(), "close", e);
		}
	}

	protected boolean evaluateTotalCount(Paging paging, String sql, Object... params) {
		if (paging.getItemTotal() != -1) {
			return true;
		}
		int count = evaluateTotalCount(sql, params);
		if (count == -1) {
			return false;
		}
		paging.setItemTotal(count);
		return true;
	}

	protected int evaluateTotalCount(String sql, Object... params) {
		String upperCaseSql = sql.toUpperCase();
		int index = upperCaseSql.indexOf(" FROM ");
		if (index == -1) {
			throw new RuntimeException("Invalid SQL statement:" + sql);
		}
		sql = sql.substring(index);
		upperCaseSql = sql.toUpperCase();
		index = upperCaseSql.indexOf(" ORDER BY ");
		if (index == -1) {
			index = upperCaseSql.indexOf(" LIMIT ");
		}
		if (index != -1) {
			sql = sql.substring(0, index);
		}
		StringBuilder builder = new StringBuilder();
		builder.append("SELECT COUNT(*)").append(sql);
		final String countSql = builder.toString();
		int result = query(countSql, new ResultsetHandler<Integer>() {
			public Integer handle(ResultSet rs) throws SQLException {
				if (!rs.next()) {
					return -1;
				}
				return rs.getInt(1);
			}
		}, params);
		return result;
	}

	protected <T> T prepareExecute(String sql, boolean isCache, ParamBinder paramBinder,
			ResultsetHandler<T> resultsetHandler, SQLExceptionHandler exceptionHandler) {
		T result = null;
		PreparedStatement stat = null;
		try {
			stat = prepareStatement(sql, isCache);
			if (paramBinder != null) {
				paramBinder.bind(stat);
			}
			ResultSet rs = stat.executeQuery();
			if (resultsetHandler != null) {
				result = resultsetHandler.handle(rs);
			}
			rs.close();
		} catch (SQLException e) {
			if (exceptionHandler != null) {
				exceptionHandler.handle(e);
			} else {
				throw new RuntimeException(e);
			}
		} finally {
			close(stat);
		}
		return result;
	}

	protected <T> T execute(String sql, ResultsetHandler<T> resultsetHandler,
			SQLExceptionHandler exceptionHandler) {
		T result = null;
		Statement stat = null;
		try {
			stat = createStatement();
			ResultSet rs = stat.executeQuery(sql);
			if (resultsetHandler != null) {
				result = resultsetHandler.handle(rs);
			}
			rs.close();
		} catch (SQLException e) {
			if (exceptionHandler != null) {
				exceptionHandler.handle(e);
			} else {
				throw new RuntimeException(e);
			}
		} finally {
			close(stat);
		}
		return result;
	}

	protected <T, P> T query(String sql, ResultsetHandler<T> resultsetHandler, P... params) {
		return prepareExecute(sql, true, new DefaultParamBinder<P>(params), resultsetHandler, null);
	}

	protected <T, P> T query(String sql, boolean isCache, ResultsetHandler<T> resultsetHandler,
			P... params) {
		if (isCache) {
			return query(sql, resultsetHandler, params);
		} else {
			return prepareExecute(sql, false, new DefaultParamBinder<P>(params), resultsetHandler,
					null);
		}
	}

	protected <P> int update(String sql, P... params) {
		PreparedStatement stat = null;
		try {
			stat = prepareStatement(sql);
			ParamBinder paramBinder = new DefaultParamBinder<P>(params);
			paramBinder.bind(stat);
			return stat.executeUpdate();
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			close(stat);
		}
	}

	protected int[] executeBatch(List<String> sqls) {
		if (sqls == null || sqls.size() < 1) {
			return null;
		}
		Statement stat = null;
		Connection conn = null;
		try {
			stat = this.createStatement();
			for (String sql : sqls) {
				stat.addBatch(sql);
			}
			conn = stat.getConnection();
			conn.setAutoCommit(false);
			int[] result = stat.executeBatch();
			conn.commit();
			return result;
		} catch (BatchUpdateException e) {
			try {
				if (conn != null) {
					conn.rollback();
				}
			} catch (SQLException e1) {
				// ignore
				// e1.printStackTrace();
			}
			log.severe("BatchUpdateException: " + e.getMessage());
			int[] error = e.getUpdateCounts();
			for (int i : error) {
				if (Statement.EXECUTE_FAILED == i) {
					log.severe("Execute failed: " + sqls.get(i));
				}
			}
			throw new RuntimeException(e);
		} catch (SQLException e) {
			try {
				if (conn != null) {
					conn.rollback();
				}
			} catch (SQLException e1) {
				// ignore
				// e1.printStackTrace();
			}
			throw new RuntimeException(e);
		} finally {
			try {
				if (conn != null) {
					conn.setAutoCommit(true);
				}
			} catch (SQLException e) {
				// ignore
				// e.printStackTrace();
			}
			close(stat);
		}
	}

	protected List<String> loadSqlStatements(String sqlFile) {
		InputStream is = ClassLoader.getSystemResourceAsStream(sqlFile);
		if (is == null) {
			throw new RuntimeException("Can't load resource: " + sqlFile);
		}
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
		} catch (UnsupportedEncodingException e) {
			// ignore
		}
		List<String> result = new LinkedList<String>();
		StringBuilder builder = new StringBuilder();
		String line = null;
		try {
			while ((line = reader.readLine()) != null) {
				line = line.trim();
				if (line.startsWith("#") || line.equals("")) {
					continue;
				}
				builder.append(line);
				if (line.endsWith(";")) {
					result.add(builder.toString());
					builder.delete(0, builder.length());
				}
			}
		} catch (IOException e) {
			throw new RuntimeException("I/O error loading resource(moe/moe.sql): " + e.getMessage());
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) {
					// ignore
				}
			}
		}
		return result;
	}
}
